package raft

import (
	"6.824/utils"
	"time"
)

type smleader struct {
	rf                      *Raft
	nextIndex               []EntryIndex
	matchIndex              []EntryIndex
	firstIndexInCurrentTerm EntryIndex
}

func (leader *smleader) enter(oldstate State, isnewterm bool) {

	leader.nextIndex = make([]EntryIndex, len(leader.rf.peers))

	leader.matchIndex = make([]EntryIndex, len(leader.rf.peers))
	for i, _ := range leader.rf.peers {
		leader.nextIndex[i] = leader.rf.persistentContext.GetLastIndex() + 1
		leader.matchIndex[i] = 0
	}
	leader.firstIndexInCurrentTerm = leader.rf.persistentContext.GetLastIndex() + 1

	//leader.rf.appendLog(LogEntry{leader.rf.persistentContext.CurrentTerm, nil}, leader.firstIndexInCurrentTerm) //append an empty entry to enforce committing current term

	go leader.heartBeatsTicker()
}

func (leader *smleader) heartBeatsTicker() {
	rf := leader.rf
	rf.begin()
	curTerm := rf.persistentContext.CurrentTerm
	rf.end()
	for rf.killed() == false {
		rf.begin()
		if curTerm != rf.persistentContext.CurrentTerm || rf.currentState != LeaderState {
			rf.end()
			break
		}
		leader.sendHeartBeats()
		rf.end()
		time.Sleep(time.Duration(HeartBeatsTime) * time.Millisecond)
	}
}
func (leader *smleader) handleAppendResp(svrId int, prevLogIndex EntryIndex, length int, reply *RequestAppendReply) {
	if !reply.Success {
		//log.Printf("svr:%v, i:%v, t:%v, %v", svrId, reply.XIndex, reply.XTerm, reply)
		if leader.nextIndex[svrId] == prevLogIndex+1 { //next id not change
			var nx EntryIndex
			if reply.XTerm == -1 {
				nx = reply.XIndex
			} else {
				nx = prevLogIndex
				for ; nx > reply.XIndex && nx > leader.rf.persistentContext.SnapshotIndex && leader.rf.persistentContext.GetEntryTerm(nx-1) != reply.XTerm; nx-- {

				}
			}
			if nx <= leader.rf.persistentContext.SnapshotIndex {
				nx = leader.rf.persistentContext.SnapshotIndex
				//leader.sendInstallReq(svrId)
			}
			leader.nextIndex[svrId] = nx
		}
	} else {

		if prevLogIndex+EntryIndex(length) > leader.matchIndex[svrId] {
			leader.matchIndex[svrId] = prevLogIndex + EntryIndex(length)

			leader.updateCommitIndex()

			leader.nextIndex[svrId] = Max(leader.nextIndex[svrId], leader.matchIndex[svrId]+1)

			leader.rf.Log("TRCE", "svr %d matchindex change to %d, nextindex change to %d", svrId, leader.matchIndex[svrId], leader.nextIndex[svrId])
		}
	}
}
func (leader *smleader) sendAppendToServer(svrId int) {
	rf := leader.rf
	prevLogIndex := leader.nextIndex[svrId] - 1

	if prevLogIndex < leader.rf.persistentContext.SnapshotIndex {
		//prevLogIndex = leader.rf.persistentContext.SnapshotIndex
		//utils.Assert(prevLogIndex == leader.rf.persistentContext.SnapshotIndex-1)
		leader.sendInstallReq(svrId)
		return
	}
	prevlogTerm := rf.persistentContext.GetEntryTerm(prevLogIndex)

	length := rf.persistentContext.GetLastIndex() - prevLogIndex
	/*if isHeartBeats {
		length = 0
	}*/
	entries := make([]LogEntry, length)
	copy(entries, rf.persistentContext.LogEntries[rf.persistentContext.ToLocalIndex(prevLogIndex+1):rf.persistentContext.ToLocalIndex(prevLogIndex+length+1)]) //may need restrict arr size
	term := rf.persistentContext.CurrentTerm
	commitId := rf.commitIndex

	rf.Log("INFO", "send append to server %d, prevLogIndex is %d, prevLogTerm is %d, entry len is %d.", svrId, prevLogIndex, prevlogTerm, len(entries))
	go func() {
		args := RequestAppendArgs{term, rf.me, prevLogIndex, prevlogTerm, entries, commitId}
		reply := RequestAppendReply{}
		ok := rf.sendRequestAppend(svrId, &args, &reply)
		/*if isHeartBeats {
			return
		}*/
		if rf.killed() {
			return
		}
		rf.begin()
		defer rf.end()
		if !ok {
			//leader.sendAppendToServer(svrId)
			return
		}
		//log.Printf("%v %v %v", rf.persistentContext.CurrentTerm, term, reply)
		if rf.handleOthersTerm(reply.Term) {
			rf.resetTimeoutClock()
			return
		} else if rf.persistentContext.CurrentTerm != term {
			return
		}

		leader.handleAppendResp(svrId, prevLogIndex, int(length), &reply)

	}()

}
func (leader *smleader) updateCommitIndex() { //can not update commit from older terms, which is explained in figure 8
	if leader.rf.getLastLogTerm() != leader.rf.persistentContext.CurrentTerm {
		return
	}
	l := Max(leader.firstIndexInCurrentTerm, leader.rf.commitIndex)
	r := leader.rf.persistentContext.GetLastIndex()
	utils.Assert(r >= l)
	for l != r {
		mid := (l + r + 1) / 2
		if leader.checkIndexCanCommit(mid) {
			l = mid
		} else {
			r = mid - 1
		}
		//leader.rf.Log("INFO", "l:%v,r:%v", l, r)
	}
	if l == leader.rf.commitIndex {
		return
	}
	if leader.checkIndexCanCommit(l) {

		utils.Assert(l <= leader.rf.persistentContext.GetLastIndex())
		leader.rf.commitIndex = l
		leader.rf.cond.Signal()
		leader.rf.Log("INFO", "success update commit to %d", leader.rf.commitIndex)
	}
}
func (leader *smleader) checkIndexCanCommit(index EntryIndex) bool {
	utils.Assert(leader.rf.persistentContext.GetEntryTerm(index) == leader.rf.persistentContext.CurrentTerm)
	cnt := 0
	for i, v := range leader.matchIndex {
		if i == leader.rf.me {
			continue
		}
		if v >= index {
			cnt++
		}
	}
	return cnt >= len(leader.rf.peers)/2
}
func (leader *smleader) sendHeartBeats() {
	rf := leader.rf

	rf.Log("TIMR", "send heartbeats")
	for i, _ := range rf.peers {
		if i == rf.me {
			continue
		}
		leader.sendAppendToServer(i)
	}
}
func (leader *smleader) sendNewEntries() {
	rf := leader.rf

	rf.Log("TIMR", "send new entries")
	for i, _ := range rf.peers {
		if i == rf.me {
			continue
		}
		leader.sendAppendToServer(i)
	}
}

func (leader *smleader) sendInstallReq(svrId int) {
	rf := leader.rf
	rf.Log("SNAP", "send snap to svr %v, lstIndex:%v, lstTerm:%v", svrId, rf.persistentContext.SnapshotIndex, rf.persistentContext.SnapshotTerm)

	args := RequestInstallSnapshotArgs{rf.persistentContext.CurrentTerm, rf.me, rf.persistentContext.SnapshotIndex, rf.persistentContext.SnapshotTerm, rf.persister.ReadSnapshot()}
	go func() {
		reply := RequestInstallSnapshotReply{}
		ok := rf.sendRequestInstall(svrId, &args, &reply)
		if ok {
			rf.begin()
			defer rf.end()
			if !rf.handleOthersTerm(reply.Term) && rf.currentState == LeaderState {
				leader.nextIndex[svrId] = Max(leader.nextIndex[svrId], args.LastIncludedIndex+1)
				leader.matchIndex[svrId] = Max(leader.matchIndex[svrId], args.LastIncludedIndex)
				if args.LastIncludedIndex > leader.matchIndex[svrId] {
					leader.matchIndex[svrId] = args.LastIncludedIndex
					leader.updateCommitIndex()
				}
			}
		}
	}()
}
